package server

import (
	"log"
	"net"
	. "tcp_server/conf"
	. "tcp_server/protocol"
	"time"
)

/**
 * @Description: 启动tcp服务
 */
func ListenServer() {
	tcpAddr, err := net.ResolveTCPAddr("tcp", Config.ServerIp)
	if err != nil {
		log.Panic("server ip address resoluted failed:", err)
	}

	listener, err := net.ListenTCP("tcp", tcpAddr)
	log.Println("start Tcp server, listening tcpAddr", tcpAddr)
	if err != nil {
		log.Panic("listen address failed", err)
	}
	defer listener.Close()

	// 开启定时关闭超时未认证的连接
	go closeUnAuthConn()

	for {
		// 接入客户连接，封装信息
		conn, _ := listener.Accept()
		connClient := CreateClientConn(conn)

		// 开启认证通信协程
		go auth(connClient)
		// 发送公钥
		go sendPublicKey(connClient)
	}
}

// 认证前的通信
func auth(conn *ClientConn) {
	// 如果认证成功则可以正常通信了
	if readAuth(conn) {
		conn.LogConn("auth successful")
		// 删除其在未认证的集合
		UnAuthConnection.Delete(conn.ConnId)
		// 如果已经有连接则将其关闭删除，并存储现在连接到认证的集合
		if c, ok := ClientConnection.Load(conn.UserId); ok {
			client := c.(*ClientConn)
			if client.Addr != conn.Addr {
				client.Close()
				ClientConnection.Store(conn.UserId, conn)
				conn.LogConn(client.Info(), "change address")
				// 开启认证后的加密通信
				communication(conn)
			}
		} else {
			ClientConnection.Store(conn.UserId, conn)
			// 开启认证后的加密通信
			communication(conn)
		}
	}
}

/**
 * @Description: 在认证之前的通信
 * @return bool: 认证成功：true，认证失败：false
 */
func readAuth(conn *ClientConn) bool {
loop:
	for {
		// 读取信息，内部进行了粘包处理
		data, err := conn.Read()
		if err != nil {
			conn.LogError("read data failed")
			break
		}
		// 解析信息到Message中,解析失败不会进行关闭连接
		msg, err1 := PackMessageFromByte(data)
		if err1 != nil {
			conn.LogError("pack message failed:", string(data))
			continue
		}
		conn.LogRead(msg.String())

		switch msg.Type {
		case UP_FUB:
			// 获取公钥失败指令,再发发送公钥给客户
			sendPublicKey(conn)
		case UP_SEE:
			if conn.UpSeeTimes >= UP_SEE_TIMES {
				conn.LogConn("receive sessionKey more than", UP_SEE_TIMES, "times")
				break loop
			}
			conn.UpSeeTimes++
			// 获取客户会话密钥信息
			if setSessionKey(msg, conn) {
				// 解密成功，通知用户发送口令
				_, err = conn.Write(MSG_DW_PWD.JsonByte())
				conn.LogWrite(MSG_DW_PWD.String())
			} else {
				// 解密失败，重新发送公钥
				sendPublicKey(conn)
			}
		case UP_PWD:
			// 验证超过次数直接关闭连接
			if conn.UpPwdTimes >= UP_PWD_TIMES {
				conn.LogConn("receive authInfo more than", UP_PWD_TIMES, "times")
				break loop
			}
			conn.UpPwdTimes++
			// 用户口令认证
			if authUser(msg, conn) {
				// 发送验证成功指令
				_, err = conn.Write(MSG_DW_SUC.JsonByte())
				conn.LogWrite(MSG_DW_SUC.String())
				return true
			} else {
				// 通知客户再次发送用户口令
				_, err = conn.Write(MSG_DW_PWD.JsonByte())
				conn.LogWrite(MSG_DW_PWD.String())
			}
		case UP_CLO:
			// 退出关闭连接
			break loop
		case UP_HEA:
			// 心跳包维持连接，不做处理
			conn.LogRead("receive heart")
		default:
			conn.LogError("not exit the message type:", msg.String())
		}
		if err != nil {
			conn.LogError("write data failed:", err)
			break
		}
	}
	conn.Close()
	return false
}

// 发送公钥给客户端
func sendPublicKey(conn *ClientConn) {
	if conn.DwPubTimes >= DW_PUB_TIMES {
		conn.LogConn("send publicKey more than", DW_PUB_TIMES, "times")
		conn.Close()
		return
	}
	msg := Message{
		Type: DW_PUB,
		Data: string(Config.PublicKey),
	}
	_, err := conn.Write(msg.JsonByte())
	conn.LogWrite("send publicKey:", msg.Data)
	if err != nil {
		conn.LogError("send publicKey failed:", err)
		conn.Close()
	}
	conn.DwPubTimes++
}

// 定时关闭未认证且超时的连接
func closeUnAuthConn() {
	ticker := time.NewTicker(5 * time.Second)
	for {
		<-ticker.C
		UnAuthConnection.Range(func(key, value interface{}) bool {
			conn := value.(*ClientConn)
			// 如果没有认证且超时状态则关闭
			if !conn.Authenticated && conn.Expire(time.Now().Unix(), Config.AuthTimeout) {
				conn.LogConn("auth timeout closed by server")
				conn.Close()
			}
			return true
		})
	}
}

// 认证后的通信
func communication(conn *ClientConn) {
	// 初始化缓存通道
	conn.ReadChan = make(chan []byte, 5)
	conn.WriteChan = make(chan []byte, 5)
	conn.StopChan = make(chan bool)

	// 开启信道读协程
	go readReceiveData(conn)
	// 测试发送数据
	go writeDataTest(conn)

	for {
		data, err := conn.ReadDecrypt()
		if err != nil {
			conn.LogError("read data failed", err)
			conn.StopChan <- true
			break
		}
		if len(data) == 0 {
			continue
		}

		conn.ReadChan <- data
	}
	conn.Close()
}
